package com.googlecode.cswish.struts.hibernate;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.Table;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.cfg.EJB3NamingStrategy;

public class CustomNamingStrategy extends EJB3NamingStrategy {

	private static final Log logger = LogFactory.getLog(CustomNamingStrategy.class);
	
	private Map<String, String> classProductNames;
	
	private Set<String> tableAnnotations;
	
	private boolean hasTableAnnotation(String tableName) {
		if (tableAnnotations == null) {
			tableAnnotations = new HashSet<>();
			
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			for (Entry<String, String> entry : HibernateJpaVendorAdapter.getClassProductNames().entrySet()) {
				String className = entry.getKey();
				String mappingTableName = null;
				try {
					Class clazz = Class.forName(className, true, classLoader);
					Table table = (Table) clazz.getAnnotation(Table.class);
					if (table != null) {
						mappingTableName = table.name();
						
						if (mappingTableName != null && mappingTableName.length()  > 0) {
							tableAnnotations.add(mappingTableName);
						}
					}
				} catch (Exception ex) {
					logger.debug("Invalid class: " + className, ex);
				}
			}
		}
		return tableAnnotations.contains(tableName);
	}
	
	private String getProductName(String tableName) {
		if (classProductNames == null) {
			classProductNames = new HashMap<>();
			
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			for (Entry<String, String> entry : HibernateJpaVendorAdapter.getClassProductNames().entrySet()) {
				// 1. try to get the name from table annotation
				String className = entry.getKey();
				String mappingTableName = null;
				try {
					Class clazz = Class.forName(className, true, classLoader);
					Table table = (Table) clazz.getAnnotation(Table.class);
					if (table != null) {
						mappingTableName = table.name();
					}
				} catch (Exception ex) {
					logger.debug("Invalid class: " + className, ex);
				}
				
				// 2. use the short class name as the default table name
				if (mappingTableName == null || mappingTableName.length()  == 0) {
					mappingTableName = className.substring(className.lastIndexOf('.') + 1).toLowerCase();
				}
				
				String productName = entry.getValue();
				if ("icbns".equals(productName)) {			// FIXME: special case for icbns, fix the package?
					productName = ISite.DEFAULT_NAME;
				}
				
				if (classProductNames.put(mappingTableName, productName) != null) {
					logger.error("Found conflict class table: " + mappingTableName 
							+ ", please search the project and find the conflict class or add @Table to class " + className);
				}
			}
		}
		
		return classProductNames.get(tableName);
	}
	
	@Override
	public String classToTableName(String className) {
		String name = super.classToTableName(className);
		return buildTableName(name, false);
	}

	@Override
	public String tableName(String tableName) {
		String name = super.tableName(tableName);
		return buildTableName(name, true);
	}

	@Override
	public String logicalCollectionTableName(String tableName, String ownerEntityTable, String associatedEntityTable,
			String propertyName) {
		String name = super
				.logicalCollectionTableName(tableName, ownerEntityTable, associatedEntityTable, propertyName);
		return buildTableName(name, true);
	}
	
	private String buildTableName(String name, boolean isCollectionTableName) {
		// FIXME: any conflict with the product deployment if using the original name?
		if (hasTableAnnotation(name)) {		// the class defined the table annotation, use the original name
			return name;
		}
		
		String checkName;
		if (isCollectionTableName) {
			int index = name.indexOf('_');
			checkName = index > 0 ? name.substring(0, index) : name;
		} else {
			checkName = name;
		}
		
		String productName = getProductName(checkName.toLowerCase());
		if (productName == null) {		// NOTICE: dynamic model need reset the table again (see DynamicModelPersistent.getPersistentClass)
			if (logger.isDebugEnabled()) {
				if (name.indexOf('_') == -1) {
					logger.debug("Unknown product class: " + name);
				}
			}
		}
		return productName == null ? name.toLowerCase() : (productName + '_' + name).toLowerCase();
	}
}